Even though the
headline rotator is now fully functioning, there is one significant
usability issue that we should address—a headline might
scroll out of the viewable area before a user is able to click on one
of its links. This forces the user to wait until the rotator has cycled
through the full set of headlines again before getting a second chance.
We can reduce the likelihood of this problem by having the rotator
pause when the user's mouse cursor hovers anywhere within the headline.
$container.hover(function() {
headline rotatorusability issue, addressingclearTimeout(pause);
}, function() {
pause = setTimeout(headlineRotate, 250);
});
When the mouse enters the headline area, the first .hover() handler calls JavaScript's clearTimeout() function. This cancels the timer in progress, preventing from being called. When the mouse leaves, the second .hover() handler reinstates the timer, thereby invoking headlineRotate() after a 250 millisecond delay. headlineRotate()
This simple code works fine most of the time. However, if the user moves the mouse over and back out of the<div>
quickly and repeatedly, a very undesirable effect can occur. Multiple
headlines will be in motion at a time, layering on top of each other in
the visible area.
Unfortunately, we need to perform some serious surgery to remove this cancer. Before the headlineRotate() function, we'll introduce one more variable:
var rotateInProgress = false;
Now, on the very first line of our function, we can check if a rotation is currently in progress. Only if the value of rotateInProgress is false do we want the code to run again. Therefore, we wrap everything within the function in an if statement. Immediately inside this conditional, we set the variable to true, and then in the callback of the second .animate() method, we set it back to false.
var headlineRotate = function() {
if (!rotateInProgress) {
rotateInProgress = true;
currentHeadline = (oldHeadline + 1)
% headlineCount;
$('div.headline').eq(oldHeadline).animate(
{top: -hiddenPosition}, 'slow', function() {
$(this).css('top', hiddenPosition);
});
$('div.headline').eq(currentHeadline).animate(
{top: 0}, 'slow', function() {
rotateInProgress = false;
pause = setTimeout(headlineRotate, 5000);
});
oldHeadline = currentHeadline;
}
};
These few additional
lines improve our headline rotator substantially. Rapid, repeated
hovering no longer causes the headlines to pile up on top of each
other. Yet this user behavior still leaves us with one nagging problem:
the rhythm of the rotator is thrown off with two or three animations
immediately following each other, rather than all of them evenly spaced
out at five-second intervals.
The problem is that more than one timer can become active concurrently if a user mouses out of the<div> before the existing timer completes. We therefore need to put one more safeguard into place, using our pause variable as a flag indicating whether another animation is imminent. To do this, we set the variable to false
when the timeout is cleared or when one completes. Now we can test the
variable's value to make sure there is no timeout active before we put
a new one in place.
var headlineRotate = function() {
if (!rotateInProgress) {
rotateInProgress = true;
pause = false;
currentHeadline = (oldHeadline + 1)
% headlineCount;
$('div.headline').eq(oldHeadline).animate(
{top: -hiddenPosition}, 'slow', function() {
$(this).css('top', hiddenPosition);
});
$('div.headline').eq(currentHeadline).animate(
{top: 0}, 'slow', function() {
rotateInProgress = false;
if (!pause) {
pause = setTimeout(headlineRotate, 5000);
}
});
oldHeadline = currentHeadline;
}
};
if (!pause) {
pause = setTimeout(headlineRotate, 5000);
}
$container.hover(function() {
clearTimeout(pause);
pause = false;
}, function() {
if (!pause) {
pause = setTimeout(headlineRotate, 250);
}
});
At last, our headline rotator can withstand all manner of attempts by the user to thwart it.